#include "BlinnPhong.h"
#include <cmath>

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define POS(a) MAX(a, 0)

using namespace Mesh;

// 设置光照强度矩阵
void Lighting::setLighting(float L[][3])
{
	for (int i = 0; i < 3; i++)
		for (int j = 0; j < 3; j++)
			this->L[i][j] = L[i][j];
}

// 设置点光源
void Lighting::setPoint(float x, float y, float z)
{
	l = {x, y, z, 1};

	// 默认中心轴和不衰减
	center = {1, 0, 0};
	theta = 3.1415926;
	al = 0;
}

// 设置平行光源
void Lighting::setParallel(float x, float y, float z)
{
	l = {x, y, z, 0};
}

// 设置投射光源：先设置点光源，然后设置中心轴、圆锥角（度数）和衰减指数
void Lighting::setCenter(float x, float y, float z, float theta, float al)
{
	center = normalize({x, y, z});
	this->theta = theta / 180 * 3.1415926;
	this->al = al;
}

// 判断是点光源还是平行光源
bool Lighting::isPoint() const
{
	return l.w == 1 ? true : false;
}

// 设置距离衰减因子
void Lighting::setReduction(float a, float b, float c)
{
	this->a = a;
	this->b = b;
	this->c = c;
}

BlinnPhong::~BlinnPhong()
{
	for (auto it = m_lighting.begin(); it != m_lighting.end(); ++it)
		if ((*it) != nullptr)
			delete (*it);
}

// 创建光照
Lighting *BlinnPhong::createLighting()
{
	Lighting *light = new Lighting;
	m_lighting.push_back(light);
	return light;
}

// 设置镜面反射指数
void BlinnPhong::setAlpha(float alpha)
{
	m_material.alpha = alpha;
}

// 设置反射系数
void BlinnPhong::setReflection(float R[][3])
{
	for (int i = 0; i < 3; i++)
		for (int j = 0; j < 3; j++)
			m_material.R[i][j] = R[i][j];
}

// 输入观察者位置，计算光照结果
void BlinnPhong::calculateLighting(point3f viewer, Mesh::HEMesh *mesh)
{
	// 如果没有法向，就插值计算顶点法向
	if (!mesh->HasVertexNormal())
		for (int i = 0; i < mesh->VertexNum(); i++)
			mesh->m_nList.push_back(Gouraud(mesh->m_vList[i]));

	// 注意清空之前顶点颜色
	mesh->m_cList.clear();

	// 计算光照
	for (int i = 0; i < mesh->VertexNum(); i++)
	{
		vertex3f *nv = mesh->m_vList[i];
		point3f np = nv->pos;

		// 三个关键方向 l n v，n
		vector3f n = mesh->m_nList[i];
		vector3f l;
		vector3f v;

		// 初始化光照
		rgb_space color = {0, 0, 0};
		float d = 0;

		// 遍历所有光照模型计算
		for (auto lit = m_lighting.begin(); lit != m_lighting.end(); ++lit)
		{
			Lighting *light = (*lit);

			// 判断光源是否是平行光源
			if (light->isPoint())
			{
				l = normalize(light->l - np);
				v = normalize(viewer - np);

				// 计算光源距离，a+bd+cd^2 得到衰减因子
				d = distance(np, light->l);
				d = 1.0 / (light->a + light->b * d + light->c * d * d);

				// 再计算角度衰减，如果夹角是负数，那么直接衰减为零
				// 注意我们之前反转了 l，所以点积后要取相反数
				float cos_alpha = -dot(light->center, l);

				// 夹角超过圆锥角，则归零
				if (cos_alpha < cos(light->theta))
					cos_alpha = 0;

				d *= pow(cos_alpha, light->al);
			}
			else
			{
				l = normalize(-light->l);
				v = normalize(viewer - np);

				// 平行光源不衰减
				d = 1;
			}

			// 累计光照
			color = color + onePointColor(l, n, v, light, d);
		}

		// 添加颜色
		mesh->m_cList.push_back(color);
	}
}

// 计算单点光照
rgb_space BlinnPhong::onePointColor(vector3f l, vector3f n, vector3f v, Lighting *light, float d)
{
	float alpha = m_material.alpha;

	// 计算半角向量
	vector3f h = normalize(l + v);

	// 根据夹角计算衰减系数：注意先对夹角余弦取正，然后再作用指数 alpha
	float l_dot_n = POS(dot(l, n));
	float n_dot_h = pow(POS(dot(n, h)), alpha);

	// 顺序 a,d,s
	rgb_space I;
	I.r = m_material.R[0][0] * light->L[0][0] +
		  (m_material.R[1][0] * light->L[1][0] * l_dot_n +
		   m_material.R[2][0] * light->L[2][0] * n_dot_h) *
			  d;
	I.g = m_material.R[0][1] * light->L[0][1] +
		  (m_material.R[1][1] * light->L[1][1] * l_dot_n +
		   m_material.R[2][1] * light->L[2][1] * n_dot_h) *
			  d;
	I.b = m_material.R[0][2] * light->L[0][2] +
		  (m_material.R[1][2] * light->L[1][2] * l_dot_n +
		   m_material.R[2][2] * light->L[2][2] * n_dot_h) *
			  d;

	// 防止颜色越界
	I.r = MIN(MAX(I.r, 0), 1);
	I.g = MIN(MAX(I.g, 0), 1);
	I.b = MIN(MAX(I.b, 0), 1);

	return I;
}